/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package gride.plugin.executor;

import java.awt.BorderLayout;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;

/**
 *
 * @author lukasz
 */
public class ExecutorGUIComponent extends JPanel
{
    private JTextField inputArea;
    private JTextArea outputArea;

    private ExecutorOutputStream outputStream;
    private ExecutorInputStream inputStream;

    /**
     * 
     * @param echoInput
     */
    public ExecutorGUIComponent(boolean echoInput)
    {
        super();

        inputArea = new JTextField();
        inputArea.setEditable(false);

        outputArea = new JTextArea();
        outputArea.setEditable(false);

        outputStream = new ExecutorOutputStream(outputArea);

        if (echoInput)
            inputStream = new ExecutorInputStream(inputArea, outputStream);
        else
            inputStream = new ExecutorInputStream(inputArea, null);

        setLayout(new BorderLayout());

        JPanel inputPanel = new JPanel(new BorderLayout());
        inputPanel.add(new JLabel("Input: "), BorderLayout.WEST);
        inputPanel.add(inputArea, BorderLayout.CENTER);

        add(inputPanel, BorderLayout.SOUTH);
        add(new JScrollPane(outputArea), BorderLayout.CENTER);
    }


    /**
     *
     * @return
     */
    public OutputStream getOutputStream()
    {
        return outputStream;
    }


    /**
     *
     * @return
     */
    public InputStream getInputStream()
    {
        return inputStream;
    }


    /**
     *
     * @param enabled
     */
    public void setInputEnabled(boolean enabled)
    {
        inputArea.setEditable(enabled);
        inputArea.setEnabled(enabled);
    }

    /**
     *
     * @return
     */
    public JTextField getInputField()
    {
        return inputArea;
    }
}


// This stream displays the output into the given JTextArea
class ExecutorOutputStream extends OutputStream
{
    private JTextArea textArea;

    public ExecutorOutputStream(JTextArea textArea) {
        this.textArea = textArea;
    }

    synchronized public void write(int b) throws IOException
    {
        // append the character to the end of the textArea's content
        textArea.append(String.valueOf((char) b));

        // make sure the textArea is scrolled all the way down
        textArea.setCaretPosition(textArea.getText().length());
    }
}


// This stream provides access to input that the user enters into the given 
// textfield; an output stream can also be provided, for echoing input
class ExecutorInputStream extends InputStream
{
    private ArrayList<Byte> buffer;

    private final Lock lock = new ReentrantLock();
    private final Condition notEmpty = lock.newCondition();

    private PrintStream printStream;


    public ExecutorInputStream(final JTextField textField,
                               final OutputStream outputStream)
    {
        buffer = new ArrayList<Byte>();

        if (outputStream != null)
            printStream = new PrintStream(outputStream);

        // add a key event listener to the textfield, that copies input to the
        // buffer every time RETURN is pressed
        textField.addKeyListener(new KeyListener()
        {
            public void keyTyped(KeyEvent e)
            {
                if (e.getKeyChar() == '\n') // return pressed
                {
                    // copy the contents of the textfield and clear it
                    String line = textField.getText() + "\n";
                    textField.setText("");

                    // echo the input to the given output stream
                    if (outputStream != null)
                        printStream.print(line);

                    // request a lock on the stream to prevent other threads 
                    // trying to read while we are writing to the buffer
                    lock.lock();

                    try
                    {
                        // copy the input to the buffer
                        for (char chr : line.toCharArray())
                            buffer.add((byte)((int)chr));

                        // signal that input is available in the buffer
                        notEmpty.signal();
                    }
                    finally
                    {
                        // release the lock
                        lock.unlock();
                    }
                }
            }

            public void keyPressed(KeyEvent e) { }

            public void keyReleased(KeyEvent e) { }
        });
    }


    @Override
    public int read() throws IOException
    {
        // obtain a lock on this object
        lock.lock();

        try
        {
            // wait for input to appear in the buffer
            while (buffer.isEmpty())
                try
                {
                     // wait until notified that there's something in the buffer
                    notEmpty.await();  
                }
                catch (InterruptedException e)
                {

                }

            // read the first character from the buffer
            int chr = buffer.remove(0);

            return chr;
        }
        finally
        {
            // release the lock
            lock.unlock();
        }
    }


    @Override
    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }

    @Override
    public int read(byte b[], int off, int len) throws IOException
    {
        // make sure all parameters are ok
        if (b == null)
            throw new NullPointerException();

        if ((off < 0) || (len < 0) || (off+len > b.length))
            throw new IndexOutOfBoundsException();

        if (len == 0)
            return 0;


        // read the requested number of bytes and place them in the buffer
        // at the given offset
        int i;
        for (i=off; i<off+len; i++)
            try
            {
                b[i] = (byte) read();

                if (b[i] == 10)
                    return i+1;
            }
            catch (IOException e)
            {
                break;
            }

        return i;
    }


    @Override
    public int available() throws IOException
    {
        // number of bytes that can be read without waiting
        return buffer.size();
    }

}